<?php

/**
 * \file
 * Classe permettant de spécifier un système d'utilisateurs en base de
 * données.
 */

/**
 * Assistance à la gestion d'accès utilisateurs.
 */
namespace pilar\Accesses /* \cond */ ; /* \endcond */
{

/**
 * Système d'utilisateurs en base de données.
 *
 * Permet de décrire l'ensemble des composantes d'un système de gestion
 * d'utilisateurs stocké en base de données.
 *
 * \note
 * En pratique la gestion des utilisateurs ne se limite pas seulement à
 * cette forme il faudra donc inscrire cette classe dans d'autres plus
 * abstraites.
 *
 * \todo
 * Rendre possible le mappage des champs requis afin de ne pas être
 * contraint d'utiliser ceux actuellement spécifiés.
 *
 * \since 02/02/2009 \author Matthieu OVIEDO <matthieu.oviedo@gmail.com>
 */
abstract class System extends \pilar\Configurables\Configurable {

/**
 * Entité des utilisateurs.
 *
 * Champs requis : "id".
 */
private $_User = null;


/**
 * Membre de l'entité $_User liant un utilisateur et un accès.
 *
 * Membres requis : "user", "access", "params".
 */
private $_userGrant = '';


/**
 * Entité des groupes.
 *
 * Membres requis si spécifié : "id".
 */
private $_Group = null;


/**
 * Membre de l'entité $_User qui fait le lien entre les utilisateurs et les
 * groupes en terme de membres.
 */
private $_membership = '';


/**
 * Membre de l'entité $_Group pour l'inclusion d'un groupe dans un autre
 * groupe.
 *
 * Membres requis si spécifié : "member", "group".
 */
private $_groupMembership = '';


/**
 * Membre de l'entité $_Group liant un groupe à un accès.
 *
 * Membres requis si spécifié : "group", "access", "params".
 */
private $_groupGrant = '';


/**
 * Permet de savoir si on a déjà configuré le système.
 */
private $_configured = false;


/**
 * Fonction appelée pour configurer le système uniquement lorsque
 * celui-ci est utilisé pour la première fois.
 *
 * Ainsi si le système n'est pas utilisé les entités concernées ne
 * seront pas chargées inutilement.
 */
abstract protected function onConfigure();


/**
 * Configure le système si cela n'a pas encore été fait.
 */
public function configure() {
 if(!$this->_configured) {
  $this->onConfigure();
  $this->_configured = true;
 }
}


/**
 * Permet de spécifier l'entité des utilisateurs.
 *
 * \param $User Entité des utilisateurs.
 */
protected function setUserEntity(\pilar\Entities\Entity $User) {
 $this->_User = $User;
}


/**
 * Permet de spécifier le nom du membre des autorisations des utilisateurs.
 *
 * \param $memberName Nom du membre des autorisations des utilisateurs.
 */
protected function setUserGrantMemberName($memberName) {
 $this->_userGrant = $memberName;
}


/**
 * Permet de spécifier l'entité des groupes d'utilisateurs.
 *
 * \param $Group Entité des groupes d'utilisateurs.
 */
protected function setGroupEntity(\pilar\Entities\Entity $Group) {
 $this->_Group = $Group;
}


/**
 * Permet de spécifier l'entité qui inclue un groupe dans un autre.
 *
 * \param $GroupMembership Entité d'inclusion de groupes.
 */
protected function setGroupMembershipMemberName($memberName) {
 $this->_groupMembership = $memberName;
}


/**
 * Permet de spécifier l'entité des membres de groupes.
 *
 * \param $groupMembershipEntity Entité des membres de groupes.
 */
protected function setMembershipMemberName($memberName) {
 $this->_membership = $memberName;
}


/**
 * Permet de spécifier l'entité des autorisations de groupes.
 *
 * \param $GroupGrant Entité des autorisations de groupes.
 */
protected function setGroupGrantMemberName($memberName) {
 $this->_groupGrant= $memberName;
}


/**
 * Liste des identifiants des groupes, dont fait parti un utilisateur
 * donné, séparés par des virgules.
 *
 * \todo
 * Voir d'optimiser les requêtes.
 *
 * \param $user Identifiant de l'utilisateur.
 *
 * \return Tableau des identifiants de groupes.
 */
public function userGroups($user) {
 // On force le chargement des entités liées au système :
  $this->configure();

 $groups = array(); // Résultat final.

 // Groupes dans lesquels se trouve directement l'utilisateur :
  // Alias de code :
   $userGroupMemberMember = $this->_User->member($this->_membership);
  $userMemberships = $userGroupMemberMember->param('through')->newCollection();
  $userMemberships->where(
   \eq(
    $userMemberships->member($userGroupMemberMember->param('on')),
    \cst($user)
   )
  );
  foreach($userMemberships as $userMembership) {
   $groups[] = $userMembership->member(
                $userGroupMemberMember->param('to')
               )->get();
  }

  // Groupes associés :
  if ($this->_groupMembership != '') {
   // Alias de code :
    $userGroupGroupMembershipMember = 
                                 $this->_Group->member($this->_groupMembership);
   $scanning = $groups;
   while(!empty($scanning)) {
    $groupMemberships = $userGroupGroupMembershipMember
                                            ->param('through')->newCollection();
    $groupMemberships->where(
     \in(
      $groupMemberships->member($userGroupGroupMembershipMember->param('on')),
      \cstArray($scanning)
     )
    );
    $current = array();
    foreach($groupMemberships as $groupMembership) {
     $current[] =
     $groupMembership->
      member($this->_Group->member($this->_groupMembership)->param('to'))
      ->get();
    }
    $scanning = array_diff($current, $groups);
    $groups = array_merge($groups, $scanning);
   }
  }

 return array_unique($groups);
}


/**
 * Évalue si un utilisateur a oui ou non un accès dans le système
 * d'utilisateurs considéré.
 *
 * \param $user Identifiant de l'utilisateur.
 * \param $acces Identifiant de l'accès.
 * \param $params Chaîne de caractères paramétrant l'accès.
 *
 * \return \c TRUE si l'utilisateur a l'accès, \c FALSE sinon.
 */
public function userGranted($user, $access, $params) {
 // Chargement des entités liées au système :
  $this->configure();

 // Alias de code :
  $userGrantMember = $this->_User->member($this->_userGrant);

 $grants = $userGrantMember->param('through')->newCollection();
 $grants->where(
  \andy(
   \andy(
    \eq($grants->member($userGrantMember->param('on')), \cst($user)),
    \eq($grants->member($userGrantMember->param('to')), \cst($access))
   ),
   \eq($grants->params, \cst($params))
  )
 )->limit(1);

 if (!$grants->isEmpty()) return true;

 if ($this->_Group != null) {
  $groups = $this->userGroups($user);
  if (!empty($groups)) {
   // Alias de code :
    $userGroupGrantMember = $this->_Group->member($this->_groupGrant);
   $grants = $userGroupGrantMember->param('through')->newCollection();
   $grants->where(
    \andy(
     \andy(
      \in(
       $grants->member($userGroupGrantMember->param('on')),
       \cstArray($groups)
      ),
      \eq($grants->member($userGroupGrantMember->param('to')), \cst($access))
     ),
     \eq($grants->params, \cst($params))
    )
   )->limit(1);
  }
  if (!$grants->isEmpty()) return true;
 }

 return false;
}


/**
 * Fonction permettant de vérifier si un utilisateur a une permission quelconque
 * sur l'accès fourni.
 * 
 * \param $userId Identifiant numérique de l'utilisateur.
 * \param $access Identifiant numérique de l'accès.
 * \return \c TRUE s'il a au moins une permission, \c FALSE sinon.
 * \since 21/06/2009
 */
public function hasPermissionOnAccess($userId, $access) {
 // Chargement des entités liées au système :
  $this->configure();

 // Alias de code :
  $userGrantMember = $this->_User->member($this->_userGrant);
 
 $grants = $userGrantMember->param('through')->newCollection();
 $grants->where(
  \andy(
   \eq($grants->member($userGrantMember->param('on')), \cst($userId)),
   \eq($grants->member($userGrantMember->param('to')), \cst($access))
  )
 )->limit(1);

 if (!$grants->isEmpty()) {
  return true;
 }

 if ($this->_Group != null) {
  $groups = $this->userGroups($userId);
  if (!empty($groups)) {
   // Alias de code :
    $userGroupGrantMember = $this->_Group->member($this->_groupGrant);
    
   $grants = $userGroupGrantMember->param('through')->newCollection();
   $grants->where(
    \andy(
     \in(
      $grants->member($userGroupGrantMember->param('on')),
      \cstArray($groups)
     ),
     \eq($grants->member($userGroupGrantMember->param('to')), \cst($access))
    )
   )->limit(1);
  }
  if (!$grants->isEmpty()) return true;
 }

 return false;
}


} // Class.

} // Namespace.
